"""
实现 mqtt 协议的连接请求报文
"""
from umq.errors import UMQException, NoDataException
from umq.mqtt.packet import (MqttFixedHeader, MqttVariableHeader, MqttPayload,
                             MqttPacket, CONNECT)
from umq.adapters import StreamWriterAdapter, StreamReaderAdapter
from umq.codecs import read_or_raise, decode_string, bytes_to_int, decode_data_with_length
from umq.utils import gen_client_id


class ConnectVariableHeader(MqttVariableHeader):
    """连接请求的可变报文头"""
    __slots__ = ('protocol_name', 'protocol_level',
                 'connect_flag', 'keep_alive')

    # 连接标志位
    RESERVED_FLAG = 0x01
    CLEAN_SESSION_FLAG = 0x02
    WILL_FLAG = 0x04
    WILL_QOS_FLAG = 0x18
    WILL_RETAIN_FLAG = 0x20
    PASSWORD_FLAG = 0x40
    USER_NAME_FLAG = 0x80

    def __init__(self, protocol_name='MQTT', protocol_level=0x04,
                 connect_flag=0x00, keep_alive=0):
        super().__init__()
        self.protocol_name = protocol_name
        self.protocol_level = protocol_level
        self.connect_flag = connect_flag
        self.keep_alive = keep_alive

    def __repr__(self):
        return 'ConnectVariableHeader(protocol_name={}, protocol_level={}, \
                connect_flag={}, keep_alive={})'.format(
                    self.protocol_name, self.protocol_level,
                    self.connect_flag, self.keep_alive
                )

    def _set_flag(self, value, mask):
        """将标识信息保存到 connect_flag 中, 用一个字节保存"""
        if value:
            self.connect_flag |= mask
        else:
            self.connect_flag &= ~mask

    def _get_flag(self, mask):
        """从 connect_flag 获取标识信息, 返回True/False"""
        return True if self.connect_flag & mask else False

    @property
    def username_flag(self) -> bool:
        """是否设置USER_NAME_FLAG"""
        return self._get_flag(self.USER_NAME_FLAG)

    @username_flag.setter
    def username_flag(self, value: bool):
        self._set_flag(value, self.USER_NAME_FLAG)

    @property
    def password_flag(self) -> bool:
        """是否设置PASSWORD_FLAG"""
        return self._get_flag(self.PASSWORD_FLAG)

    @password_flag.setter
    def password_flag(self, value: bool):
        self._set_flag(value, self.PASSWORD_FLAG)

    @property
    def will_retain_flag(self) -> bool:
        """是否设置WILL_RETAIN_FLAG"""
        return self._get_flag(self.WILL_RETAIN_FLAG)

    @will_retain_flag.setter
    def will_retain_flag(self, value: bool):
        self._set_flag(value, self.WILL_RETAIN_FLAG)

    @property
    def will_flag(self) ->bool:
        """是否设置WILL_FLAG"""
        return self._get_flag(self.WILL_FLAG)

    @will_flag.setter
    def will_flag(self, value: bool):
        self._set_flag(value, self.WILL_FLAG)

    @property
    def clean_session_flag(self) -> bool:
        """是否设置CLEAN_SESSION_FLAG"""
        return self._get_flag(self.CLEAN_SESSION_FLAG)

    @clean_session_flag.setter
    def clean_session_flag(self, value: bool):
        self._set_flag(value, self.CLEAN_SESSION_FLAG)

    @property
    def will_qos(self):
        """获取qos的值"""
        return (self.connect_flag & 0x18) >> 3

    @will_qos.setter
    def will_qos(self, value: int):
        # 先将qos重置
        self.connect_flag &= 0xE7
        self.connect_flag |= (value << 3)

    @classmethod
    async def from_stream(cls, reader: StreamWriterAdapter, fixed: MqttFixedHeader):
        """从缓冲区读取 variable_header"""
        # 协议名
        protocol_name = await decode_string(reader)

        # 协议级别
        protocol_level_byte = await read_or_raise(reader, 1)
        protocol_level = bytes_to_int(protocol_level_byte)

        # 连接标志
        connect_flag_byte = await read_or_raise(reader, 1)
        connect_flag = bytes_to_int(connect_flag_byte)

        # keep alive
        keep_alive_byte = await read_or_raise(reader, 2)
        keep_alive = bytes_to_int(keep_alive_byte)

        return cls(connect_flag, keep_alive, protocol_name, protocol_level)


class ConnectPayload(MqttPayload):
    """连接请求的有效载荷报文头"""

    __slots__ = (
        'client_id', 'will_topic', 'will_message', 'username', 'password'
    )

    def __init__(self, client_id=None, will_topic=None, will_message=None,
                 username=None, password=None):
        super().__init__()
        self.client_id_is_random = False
        self.client_id = client_id
        self.will_topic = will_topic
        self.will_message = will_message
        self.username = username
        self.password = password

    def __repr__(self):
        return 'ConnectPayload(client_id={}, will_topic={}, \
                will_message={})'.format(
                    self.client_id, self.will_topic,
                    self.will_message, self.username
                )

    @classmethod
    async def from_stream(cls, reader: StreamReaderAdapter,
                          fixed: MqttFixedHeader, variable: ConnectVariableHeader):
        payload = cls()
        # 读取client id
        try:
            payload.client_id = await decode_string(reader)
        except NoDataException:
            payload.client_id = None

        # 协议允许client id为空，当为空的时候需要生成一个
        if payload.client_id is None or payload.client_id == '':
            payload.client_id = gen_client_id()
            payload.client_id_is_random = True

        if variable.will_flag:
            try:
                payload.will_topic = await decode_string(reader)
                payload.will_message = await decode_data_with_length(reader)
            except NoDataException:
                payload.will_topic = None
                payload.will_message = None

        if variable.username_flag:
            try:
                payload.username = await decode_string(reader)
            except NoDataException:
                payload.username = None

        if variable.password_flag:
            try:
                payload.password = await decode_string(reader)
            except NoDataException:
                payload.password = None

        return payload


class ConnectPacket(MqttPacket):
    """Connect 包实现"""
    VARIABLE_HEADER = ConnectVariableHeader
    PAYLOAD = ConnectPayload

    @property
    def protocol_name(self):
        return self.variable.protocol_name

    @protocol_name.setter
    def protocol_name(self, name: str):
        self.variable.protocol_name = name

    @property
    def protocol_level(self):
        return self.variable.protocol_level

    @protocol_level.setter
    def protocol_level(self, level):
        self.variable.protocol_level = level

    @property
    def username_flag(self):
        return self.variable.username_flag

    @username_flag.setter
    def username_flag(self, flag):
        self.variable.username_flag = flag

    @property
    def password_flag(self):
        return self.variable.password_flag

    @password_flag.setter
    def password_flag(self, flag):
        self.variable.password_flag = flag

    @property
    def clean_session_flag(self):
        return self.variable.clean_session_flag

    @clean_session_flag.setter
    def clean_session_flag(self, flag):
        self.variable.clean_session_flag = flag

    @property
    def will_retain_flag(self):
        return self.variable.will_retain_flag

    @will_retain_flag.setter
    def will_retain_flag(self, flag):
        self.variable.will_retain_flag = flag

    @property
    def will_qos(self):
        return self.variable.will_qos

    @will_qos.setter
    def will_qos(self, flag):
        self.variable.will_qos = flag

    @property
    def will_flag(self):
        return self.variable.will_flag

    @will_flag.setter
    def will_flag(self, flag):
        self.variable.will_flag = flag

    @property
    def client_id(self):
        return self.payload.client_id

    @client_id.setter
    def client_id(self, client_id):
        self.payload.client_id = client_id

    @property
    def client_id_is_random(self):
        return self.payload.client_id_is_random

    @client_id_is_random.setter
    def client_id_is_random(self, client_id_is_random: bool):
        self.payload.client_id_is_random = client_id_is_random

    @property
    def will_topic(self):
        return self.payload.will_topic

    @will_topic.setter
    def will_topic(self, will_topic: str):
        self.payload.payload.will_topic = will_topic

    @property
    def will_message(self):
        return self.payload.will_message

    @will_message.setter
    def will_message(self, will_message: str):
        self.payload.will_message = will_message

    @property
    def username(self):
        return self.payload.username

    @username.setter
    def username(self, name):
        self.payload.username = name

    @property
    def password(self):
        return self.payload.password

    @password.setter
    def payload(self, ps):
        self.payload.password = ps

    @property
    def keep_alive(self):
        return self.payload.keep_alive

    @keep_alive.setter
    def keep_alive(self, keep_alive):
        self.payload.keep_alive = keep_alive

    def __init__(self, fixed: MqttFixedHeader = None,
                 variable: ConnectVariableHeader = None,
                 payload: ConnectPayload = None):
        if fixed is None:
            header = MqttFixedHeader(CONNECT)
        else:
            if fixed.packet_type is not CONNECT:
                raise UMQException(
                    'Invalid fixed packet type: %s' % fixed.packetType
                )
            header = fixed
        super().__init__(header)
        self.variable = variable
        self.payload = payload
